using System;
using ch.ethz.ssh2.util;
namespace ch.ethz.ssh2
{
	
	/// <summary> A very basic <code>SCPClient</code> that can be used to copy files from/to
	/// the SSH-2 server. On the server side, the "scp" program must be in the PATH.
	/// <p>
	/// This scp client is thread safe - you can download (and upload) different sets
	/// of files concurrently without any troubles. The <code>SCPClient</code> is
	/// actually mapping every request to a distinct {@link Session}.
	/// 
	/// </summary>
	/// <author>  Christian Plattner
	/// </author>
	/// <version>  2.50, 03/15/10
	/// </version>
	
	public class SCPClient
	{
		internal Connection conn;
		
		//UPGRADE_NOTE: Field 'EnclosingInstance' was added to class 'LenNamePair' to access its enclosing instance. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1019'"
		internal class LenNamePair
		{
			public LenNamePair(SCPClient enclosingInstance)
			{
				InitBlock(enclosingInstance);
			}
			private void  InitBlock(SCPClient enclosingInstance)
			{
				this.enclosingInstance = enclosingInstance;
			}
			private SCPClient enclosingInstance;
			public SCPClient Enclosing_Instance
			{
				get
				{
					return enclosingInstance;
				}
				
			}
			internal long length;
			internal System.String filename;
		}
		
		public SCPClient(Connection conn)
		{
			if (conn == null)
				throw new System.ArgumentException("Cannot accept null argument!");
			this.conn = conn;
		}
		
		private void  readResponse(System.IO.Stream input)
		{
			int c = input.ReadByte();
			
			if (c == 0)
				return ;
			
			if (c == - 1)
				throw new System.IO.IOException("Remote scp terminated unexpectedly.");
			
			if ((c != 1) && (c != 2))
				throw new System.IO.IOException("Remote scp sent illegal error code.");
			
			if (c == 2)
				throw new System.IO.IOException("Remote scp terminated with error.");
			
			System.String err = receiveLine(input);
			throw new System.IO.IOException("Remote scp terminated with error (" + err + ").");
		}
		
		private System.String receiveLine(System.IO.Stream input)
		{
			System.Text.StringBuilder sb = new System.Text.StringBuilder(30);
			
			while (true)
			{
				/* This is a random limit - if your path names are longer, then adjust it */
				
				if (sb.Length > 8192)
					throw new System.IO.IOException("Remote scp sent a too long line");
				
				int c = input.ReadByte();
				
				if (c < 0)
					throw new System.IO.IOException("Remote scp terminated unexpectedly.");
				
				if (c == '\n')
					break;
				
				sb.Append((char) c);
			}
			return sb.ToString();
		}
		
		private LenNamePair parseCLine(System.String line)
		{
			/* Minimum line: "xxxx y z" ---> 8 chars */
			
			long len;
			
			if (line.Length < 8)
				throw new System.IO.IOException("Malformed C line sent by remote SCP binary, line too short.");
			
			if ((line[4] != ' ') || (line[5] == ' '))
				throw new System.IO.IOException("Malformed C line sent by remote SCP binary.");
			
			//UPGRADE_WARNING: Method 'java.lang.String.indexOf' was converted to 'System.String.IndexOf' which may throw an exception. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1101'"
			int length_name_sep = line.IndexOf(' ', 5);
			
			if (length_name_sep == - 1)
				throw new System.IO.IOException("Malformed C line sent by remote SCP binary.");
			
			System.String length_substring = line.Substring(5, (length_name_sep) - (5));
			System.String name_substring = line.Substring(length_name_sep + 1);
			
			if ((length_substring.Length <= 0) || (name_substring.Length <= 0))
				throw new System.IO.IOException("Malformed C line sent by remote SCP binary.");
			
			if ((6 + length_substring.Length + name_substring.Length) != line.Length)
				throw new System.IO.IOException("Malformed C line sent by remote SCP binary.");
			
			try
			{
				len = System.Int64.Parse(length_substring);
			}
			catch (System.FormatException e)
			{
				throw new System.IO.IOException("Malformed C line sent by remote SCP binary, cannot parse file length.");
			}
			
			if (len < 0)
				throw new System.IO.IOException("Malformed C line sent by remote SCP binary, illegal file length.");
			
			LenNamePair lnp = new LenNamePair(this);
			lnp.length = len;
			lnp.filename = name_substring;
			
			return lnp;
		}
		
		private void  sendBytes(Session sess, byte[] data, System.String fileName, System.String mode)
		{
			System.IO.Stream os = sess.Stdin;
			System.IO.Stream input = new System.IO.BufferedStream(sess.Stdout, 512);
			
			readResponse(input);
			
			System.String cline = "C" + mode + " " + data.Length + " " + fileName + "\n";
			
			if (os is ch.ethz.ssh2.channel.ChannelOutputStream)
				((ch.ethz.ssh2.channel.ChannelOutputStream) os).write(StringEncoder.GetBytes(cline));
			else
			{
				byte[] temp_byteArray;
				temp_byteArray = StringEncoder.GetBytes(cline);
				os.Write(temp_byteArray, 0, temp_byteArray.Length);
			}
			os.Flush();
			
			readResponse(input);
			
			os.Write(data, 0, data.Length);
			os.WriteByte((System.Byte) 0);
			os.Flush();
			
			readResponse(input);
			
			if (os is ch.ethz.ssh2.channel.ChannelOutputStream)
				((ch.ethz.ssh2.channel.ChannelOutputStream) os).write(StringEncoder.GetBytes("E\n"));
			else
			{
				byte[] temp_byteArray2;
				temp_byteArray2 = StringEncoder.GetBytes("E\n");
				os.Write(temp_byteArray2, 0, temp_byteArray2.Length);
			}
			os.Flush();
		}
		
		private void  sendFiles(Session sess, System.String[] files, System.String[] remoteFiles, System.String mode)
		{
			byte[] buffer = new byte[8192];
			
			System.IO.Stream os = new System.IO.BufferedStream(sess.Stdin, 40000);
			System.IO.Stream input = new System.IO.BufferedStream(sess.Stdout, 512);
			
			readResponse(input);
			
			for (int i = 0; i < files.Length; i++)
			{
				System.IO.FileInfo f = new System.IO.FileInfo(files[i]);
				long remain = 0;
                if (f.Exists)
                    remain = f.Length;
				
				System.String remoteName;
				
				if ((remoteFiles != null) && (remoteFiles.Length > i) && (remoteFiles[i] != null))
					remoteName = remoteFiles[i];
				else
					remoteName = f.Name;
				
				System.String cline = "C" + mode + " " + remain + " " + remoteName + "\n";
				
				if (os is ch.ethz.ssh2.channel.ChannelOutputStream)
					((ch.ethz.ssh2.channel.ChannelOutputStream) os).write(StringEncoder.GetBytes(cline));
				else
				{
					byte[] temp_byteArray;
					temp_byteArray = StringEncoder.GetBytes(cline);
					os.Write(temp_byteArray, 0, temp_byteArray.Length);
				}
				os.Flush();
				
				readResponse(input);
				
				System.IO.FileStream fis = null;
				
				try
				{
					//UPGRADE_TODO: Constructor 'java.io.FileInputStream.FileInputStream' was converted to 'System.IO.FileStream.FileStream' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioFileInputStreamFileInputStream_javaioFile'"
					fis = new System.IO.FileStream(f.FullName, System.IO.FileMode.Open, System.IO.FileAccess.Read);
					
					while (remain > 0)
					{
						int trans;
						if (remain > buffer.Length)
							trans = buffer.Length;
						else
							trans = (int) remain;
						
						if (Utility.ReadInput(fis, buffer, 0, trans) != trans)
							throw new System.IO.IOException("Cannot read enough from local file " + files[i]);
						
						os.Write(buffer, 0, trans);
						
						remain -= trans;
					}
				}
				finally
				{
					if (fis != null)
						fis.Close();
				}
				
				os.WriteByte((System.Byte) 0);
				os.Flush();
				
				readResponse(input);
			}
			
			if (os is ch.ethz.ssh2.channel.ChannelOutputStream)
				((ch.ethz.ssh2.channel.ChannelOutputStream) os).write(StringEncoder.GetBytes("E\n"));
			else
			{
				byte[] temp_byteArray2;
				temp_byteArray2 = StringEncoder.GetBytes("E\n");
				os.Write(temp_byteArray2, 0, temp_byteArray2.Length);
			}
			os.Flush();
		}
		
		private void  receiveFiles(Session sess, System.IO.Stream[] targets)
		{
			byte[] buffer = new byte[8192];
			
			System.IO.Stream os = new System.IO.BufferedStream(sess.Stdin, 512);
			System.IO.Stream input = new System.IO.BufferedStream(sess.Stdout, 40000);
			
			os.WriteByte((System.Byte) 0x0);
			os.Flush();
			
			for (int i = 0; i < targets.Length; i++)
			{
				LenNamePair lnp = null;
				
				while (true)
				{
					int c = input.ReadByte();
					if (c < 0)
						throw new System.IO.IOException("Remote scp terminated unexpectedly.");
					
					System.String line = receiveLine(input);
					
					if (c == 'T')
					{
						/* Ignore modification times */
						
						continue;
					}
					
					if ((c == 1) || (c == 2))
						throw new System.IO.IOException("Remote SCP error: " + line);
					
					if (c == 'C')
					{
						lnp = parseCLine(line);
						break;
					}
					throw new System.IO.IOException("Remote SCP error: " + ((char) c) + line);
				}
				
				os.WriteByte((System.Byte) 0x0);
				os.Flush();
				
				long remain = lnp.length;
				
				while (remain > 0)
				{
					int trans;
					if (remain > buffer.Length)
						trans = buffer.Length;
					else
						trans = (int) remain;
					
					int this_time_received = (input is ch.ethz.ssh2.StreamGobbler || input is ch.ethz.ssh2.channel.ChannelInputStream)?(int) Utility.InvokeMethodAsVirtual(input, "read", new System.Object[]{buffer, 0, trans}):Utility.ReadInput(input, buffer, 0, trans);
					
					if (this_time_received < 0)
					{
						throw new System.IO.IOException("Remote scp terminated connection unexpectedly");
					}
					
					targets[i].Write(buffer, 0, this_time_received);
					
					remain -= this_time_received;
				}
				
				readResponse(input);
				
				os.WriteByte((System.Byte) 0x0);
				os.Flush();
			}
		}
		
		private void  receiveFiles(Session sess, System.String[] files, System.String target)
		{
			byte[] buffer = new byte[8192];
			
			System.IO.Stream os = new System.IO.BufferedStream(sess.Stdin, 512);
			System.IO.Stream input = new System.IO.BufferedStream(sess.Stdout, 40000);
			
			os.WriteByte((System.Byte) 0x0);
			os.Flush();
			
			for (int i = 0; i < files.Length; i++)
			{
				LenNamePair lnp = null;
				
				while (true)
				{
					int c = input.ReadByte();
					if (c < 0)
						throw new System.IO.IOException("Remote scp terminated unexpectedly.");
					
					System.String line = receiveLine(input);
					
					if (c == 'T')
					{
						/* Ignore modification times */
						
						continue;
					}
					
					if ((c == 1) || (c == 2))
						throw new System.IO.IOException("Remote SCP error: " + line);
					
					if (c == 'C')
					{
						lnp = parseCLine(line);
						break;
					}
					throw new System.IO.IOException("Remote SCP error: " + ((char) c) + line);
				}
				
				os.WriteByte((System.Byte) 0x0);
				os.Flush();
				
				System.IO.FileInfo f = new System.IO.FileInfo(target + System.IO.Path.DirectorySeparatorChar + lnp.filename);
				System.IO.FileStream fop = null;
				
				try
				{
					//UPGRADE_TODO: Constructor 'java.io.FileOutputStream.FileOutputStream' was converted to 'System.IO.FileStream.FileStream' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioFileOutputStreamFileOutputStream_javaioFile'"
					fop = new System.IO.FileStream(f.FullName, System.IO.FileMode.Create);
					
					long remain = lnp.length;
					
					while (remain > 0)
					{
						int trans;
						if (remain > buffer.Length)
							trans = buffer.Length;
						else
							trans = (int) remain;
						
						int this_time_received = (input is ch.ethz.ssh2.StreamGobbler || input is ch.ethz.ssh2.channel.ChannelInputStream)?(int) Utility.InvokeMethodAsVirtual(input, "read", new System.Object[]{buffer, 0, trans}):Utility.ReadInput(input, buffer, 0, trans);
						
						if (this_time_received < 0)
						{
							throw new System.IO.IOException("Remote scp terminated connection unexpectedly");
						}
						
						fop.Write(buffer, 0, this_time_received);
						
						remain -= this_time_received;
					}
				}
				finally
				{
					if (fop != null)
						fop.Close();
				}
				
				readResponse(input);
				
				os.WriteByte((System.Byte) 0x0);
				os.Flush();
			}
		}
		
		/// <summary> Copy a local file to a remote directory, uses mode 0600 when creating
		/// the file on the remote side.
		/// 
		/// </summary>
		/// <param name="localFile">Path and name of local file.
		/// </param>
		/// <param name="remoteTargetDirectory">Remote target directory. Use an empty string to specify the default directory.
		/// 
		/// </param>
		/// <throws>  IOException </throws>
		public virtual void  putFile(System.String localFile, System.String remoteTargetDirectory)
		{
			putFile(new System.String[]{localFile}, remoteTargetDirectory, "0600");
		}
		
		/// <summary> Copy a set of local files to a remote directory, uses mode 0600 when
		/// creating files on the remote side.
		/// 
		/// </summary>
		/// <param name="localFiles">Paths and names of local file names.
		/// </param>
		/// <param name="remoteTargetDirectory">Remote target directory. Use an empty string to specify the default directory.
		/// 
		/// </param>
		/// <throws>  IOException </throws>
		
		public virtual void  put(System.String[] localFiles, System.String remoteTargetDirectory)
		{
			putFile(localFiles, remoteTargetDirectory, "0600");
		}
		
		/// <summary> Copy a local file to a remote directory, uses the specified mode when
		/// creating the file on the remote side.
		/// 
		/// </summary>
		/// <param name="localFile">Path and name of local file.
		/// </param>
		/// <param name="remoteTargetDirectory">Remote target directory. Use an empty string to specify the default directory.
		/// </param>
		/// <param name="mode">a four digit string (e.g., 0644, see "man chmod", "man open")
		/// </param>
		/// <throws>  IOException </throws>
		public virtual void  putFile(System.String localFile, System.String remoteTargetDirectory, System.String mode)
		{
			putFile(new System.String[]{localFile}, remoteTargetDirectory, mode);
		}
		
		/// <summary> Copy a local file to a remote directory, uses the specified mode and remote filename
		/// when creating the file on the remote side.
		/// 
		/// </summary>
		/// <param name="localFile">Path and name of local file.
		/// </param>
		/// <param name="remoteFileName">The name of the file which will be created in the remote target directory.
		/// </param>
		/// <param name="remoteTargetDirectory">Remote target directory. Use an empty string to specify the default directory.
		/// </param>
		/// <param name="mode">a four digit string (e.g., 0644, see "man chmod", "man open")
		/// </param>
		/// <throws>  IOException </throws>
		public virtual void  putFile(System.String localFile, System.String remoteFileName, System.String remoteTargetDirectory, System.String mode)
		{
			putFile(new System.String[]{localFile}, new System.String[]{remoteFileName}, remoteTargetDirectory, mode);
		}
		
		/// <summary> Create a remote file and copy the contents of the passed byte array into it.
		/// Uses mode 0600 for creating the remote file.
		/// 
		/// </summary>
		/// <param name="data">the data to be copied into the remote file.
		/// </param>
		/// <param name="remoteFileName">The name of the file which will be created in the remote target directory.
		/// </param>
		/// <param name="remoteTargetDirectory">Remote target directory. Use an empty string to specify the default directory.
		/// </param>
		/// <throws>  IOException </throws>
		
		public virtual void  putFile(byte[] data, System.String remoteFileName, System.String remoteTargetDirectory)
		{
			putFile(data, remoteFileName, remoteTargetDirectory, "0600");
		}
		
		/// <summary> Create a remote file and copy the contents of the passed byte array into it.
		/// The method use the specified mode when creating the file on the remote side.
		/// 
		/// </summary>
		/// <param name="data">the data to be copied into the remote file.
		/// </param>
		/// <param name="remoteFileName">The name of the file which will be created in the remote target directory.
		/// </param>
		/// <param name="remoteTargetDirectory">Remote target directory. Use an empty string to specify the default directory.
		/// </param>
		/// <param name="mode">a four digit string (e.g., 0644, see "man chmod", "man open")
		/// </param>
		/// <throws>  IOException </throws>
		public virtual void  putFile(byte[] data, System.String remoteFileName, System.String remoteTargetDirectory, System.String mode)
		{
			Session sess = null;
			
			if ((remoteFileName == null) || (remoteTargetDirectory == null) || (mode == null))
				throw new System.ArgumentException("Null argument.");
			
			if (mode.Length != 4)
				throw new System.ArgumentException("Invalid mode.");
			
			for (int i = 0; i < mode.Length; i++)
				if (System.Char.IsDigit(mode[i]) == false)
					throw new System.ArgumentException("Invalid mode.");
			
			remoteTargetDirectory = remoteTargetDirectory.Trim();
			remoteTargetDirectory = (remoteTargetDirectory.Length > 0)?remoteTargetDirectory:".";
			
			System.String cmd = "scp -t -d " + remoteTargetDirectory;
			
			try
			{
				sess = conn.openSession();
				sess.execCommand(cmd);
				sendBytes(sess, data, remoteFileName, mode);
			}
			catch (System.IO.IOException e)
			{
				throw (System.IO.IOException) new System.IO.IOException("Error during SCP transfer.",e);
			}
			finally
			{
				if (sess != null)
					sess.close();
			}
		}
		
		/// <summary> Copy a set of local files to a remote directory, uses the specified mode
		/// when creating the files on the remote side.
		/// 
		/// </summary>
		/// <param name="localFiles">Paths and names of the local files.
		/// </param>
		/// <param name="remoteTargetDirectory">Remote target directory. Use an empty string to specify the default directory.
		/// </param>
		/// <param name="mode">a four digit string (e.g., 0644, see "man chmod", "man open")
		/// </param>
		/// <throws>  IOException </throws>
		public virtual void  putFile(System.String[] localFiles, System.String remoteTargetDirectory, System.String mode)
		{
			putFile(localFiles, null, remoteTargetDirectory, mode);
		}
		
		public virtual void  putFile(System.String[] localFiles, System.String[] remoteFiles, System.String remoteTargetDirectory, System.String mode)
		{
			Session sess = null;
			
			/* remoteFiles may be null, indicating that the local filenames shall be used */
			
			if ((localFiles == null) || (remoteTargetDirectory == null) || (mode == null))
				throw new System.ArgumentException("Null argument.");
			
			if (mode.Length != 4)
				throw new System.ArgumentException("Invalid mode.");
			
			for (int i = 0; i < mode.Length; i++)
				if (System.Char.IsDigit(mode[i]) == false)
					throw new System.ArgumentException("Invalid mode.");
			
			if (localFiles.Length == 0)
				return ;
			
			remoteTargetDirectory = remoteTargetDirectory.Trim();
			remoteTargetDirectory = (remoteTargetDirectory.Length > 0)?remoteTargetDirectory:".";
			
			System.String cmd = "scp -t -d " + remoteTargetDirectory;
			
			for (int i = 0; i < localFiles.Length; i++)
			{
				if (localFiles[i] == null)
					throw new System.ArgumentException("Cannot accept null filename.");
			}
			
			try
			{
				sess = conn.openSession();
				sess.execCommand(cmd);
				sendFiles(sess, localFiles, remoteFiles, mode);
			}
			catch (System.IO.IOException e)
			{
				throw (System.IO.IOException) new System.IO.IOException("Error during SCP transfer.",e);
			}
			finally
			{
				if (sess != null)
					sess.close();
			}
		}
		
		/// <summary> Download a file from the remote server to a local directory.
		/// 
		/// </summary>
		/// <param name="remoteFile">Path and name of the remote file.
		/// </param>
		/// <param name="localTargetDirectory">Local directory to put the downloaded file.
		/// 
		/// </param>
		/// <throws>  IOException </throws>
		public virtual void  getFile(System.String remoteFile, System.String localTargetDirectory)
		{
			getFile(new System.String[]{remoteFile}, localTargetDirectory);
		}
		
		/// <summary> Download a file from the remote server and pipe its contents into an <code>OutputStream</code>.
		/// Please note that, to enable flexible usage of this method, the <code>OutputStream</code> will not
		/// be closed nor flushed.  
		/// 
		/// </summary>
		/// <param name="remoteFile">Path and name of the remote file.
		/// </param>
		/// <param name="target">OutputStream where the contents of the file will be sent to.
		/// </param>
		/// <throws>  IOException </throws>
		public virtual void  getFile(System.String remoteFile, System.IO.Stream target)
		{
			getFile(new System.String[]{remoteFile}, new System.IO.Stream[]{target});
		}
		
		private void  getFile(System.String[] remoteFiles, System.IO.Stream[] targets)
		{
			Session sess = null;
			
			if ((remoteFiles == null) || (targets == null))
				throw new System.ArgumentException("Null argument.");
			
			if (remoteFiles.Length != targets.Length)
				throw new System.ArgumentException("Length of arguments does not match.");
			
			if (remoteFiles.Length == 0)
				return ;
			
			System.String cmd = "scp -f";
			
			for (int i = 0; i < remoteFiles.Length; i++)
			{
				if (remoteFiles[i] == null)
					throw new System.ArgumentException("Cannot accept null filename.");
				
				System.String tmp = remoteFiles[i].Trim();
				
				if (tmp.Length == 0)
					throw new System.ArgumentException("Cannot accept empty filename.");
				
				cmd += (" " + tmp);
			}
			
			try
			{
				sess = conn.openSession();
				sess.execCommand(cmd);
				receiveFiles(sess, targets);
			}
			catch (System.IO.IOException e)
			{
				throw (System.IO.IOException) new System.IO.IOException("Error during SCP transfer.",e);
			}
			finally
			{
				if (sess != null)
					sess.close();
			}
		}
		
		/// <summary> Download a set of files from the remote server to a local directory.
		/// 
		/// </summary>
		/// <param name="remoteFiles">Paths and names of the remote files.
		/// </param>
		/// <param name="localTargetDirectory">Local directory to put the downloaded files.
		/// 
		/// </param>
		/// <throws>  IOException </throws>
		public virtual void  getFile(System.String[] remoteFiles, System.String localTargetDirectory)
		{
			Session sess = null;
			
			if ((remoteFiles == null) || (localTargetDirectory == null))
				throw new System.ArgumentException("Null argument.");
			
			if (remoteFiles.Length == 0)
				return ;
			
			System.String cmd = "scp -f";
			
			for (int i = 0; i < remoteFiles.Length; i++)
			{
				if (remoteFiles[i] == null)
					throw new System.ArgumentException("Cannot accept null filename.");
				
				System.String tmp = remoteFiles[i].Trim();
				
				if (tmp.Length == 0)
					throw new System.ArgumentException("Cannot accept empty filename.");
				
				cmd += (" " + tmp);
			}
			
			try
			{
				sess = conn.openSession();
				sess.execCommand(cmd);
				receiveFiles(sess, remoteFiles, localTargetDirectory);
			}
			catch (System.IO.IOException e)
			{
				throw (System.IO.IOException) new System.IO.IOException("Error during SCP transfer.",e);
			}
			finally
			{
				if (sess != null)
					sess.close();
			}
		}
	}
}